package dav.mobile.garuda.states;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Body;
import com.badlogic.gdx.physics.box2d.World;

import dav.mobile.garuda.components.Constants;
import dav.mobile.garuda.components.ResourceManager;
import dav.mobile.garuda.components.SoundManager;
import dav.mobile.garuda.components.Utilities;
import dav.mobile.garuda.states.game.GameObject;
import dav.mobile.garuda.states.game.objects.Floor;
import dav.mobile.garuda.states.game.objects.Garuda;
import dav.mobile.garuda.states.game.objects.encounters.*;
import dav.mobile.garuda.states.game.threads.ForceThread;

public class StateGame extends State implements InputProcessor {
	//Game attributes
	private int		m_StageLength;
	private int 	m_Score1;
	private int 	m_Score2;
	private int 	m_Score3;
	
	private int 	m_CurrentScore;
	private int 	m_CurrentLength;
	
	private float	m_Time;
	private float 	m_LastPosX;
	
	private int 	m_Life;
	
	private	int 	m_Place;
	
	private String	m_GamePlace;
	private int 	m_GameNum;
	
	private Music	m_Music;
	
	//Encounter array
	private ArrayList<Encounter> m_Encounters;
	
	//Game flag
	private boolean m_Init;
	private boolean m_Finish;
	private boolean m_Paused;
	
	//Camera
	OrthographicCamera m_Camera;
	OrthographicCamera m_HUDCamera;
	
	//Render level
	int 			m_StageWidth;
	int 			m_StageHeight;
	
	TextureRegion	m_Background;
	float 			m_BackgroundWidth;
	float 			m_BackgroundHeight;
	
	//Sprite
	TextureRegion	m_Aira;
	TextureRegion	m_Goal;
	int 			m_SpriteY;
	
	//Game objects
	Garuda	m_Garuda;
	Floor	m_Floor;	
	
	//Physics level
	World 			m_World;
	
	//Force
	private boolean 	m_GetForce;
	private Vector2 	m_Force;

	//Load and create game attributes from JSON
	public void LoadJSON(JSONObject m_JSON, int place) throws JSONException {
		//Load stage information
		m_StageLength = m_JSON.getInt("stage-length") * 6;
		m_CurrentLength = m_StageLength;
		
		m_Score1 = m_JSON.getInt("stage-score1");
		m_Score2 = m_JSON.getInt("stage-score2");
		m_Score3 = m_JSON.getInt("stage-score3");
		
		//Set background
		m_Background 	= ResourceManager.m_GameBackgrounds[place];
		
		m_BackgroundWidth = 1.33f * m_Background.getRegionWidth();
		m_BackgroundHeight = 1.33f * m_Background.getRegionHeight();
		
		//Create Physics World
		m_World = new World(new Vector2(0, -10), true);
		
		//Create floor
		m_Floor = new Floor(place, m_StageLength, m_World);
	
		//Create Garuda
		m_Garuda 	= new Garuda(100, 100, m_World);
		m_LastPosX	= m_Garuda.getPosX();
		
		//Set stage width and height
		m_StageWidth 	= m_StageLength * (int) Constants.PIXELS_PER_METER;
		m_StageHeight 	= (int)m_BackgroundHeight;

		//Load encounters
		m_Encounters = new ArrayList<Encounter>();
		if (m_JSON.has("stage-encounters")) {
			JSONArray EncounterTemp = m_JSON.getJSONArray("stage-encounters");
			for (int i=0;i<EncounterTemp.length();i++) {
				//Get object
				JSONObject enc = EncounterTemp.getJSONObject(i);
				
				//Randomize encounter number
				int min = enc.getInt("num-min");
				int max = enc.getInt("num-max");
				
				Random r = new Random();
				int num = r.nextInt(max-min);
				num += min;
				
				//Create encounter
				for (int j=0;j<num;j++) {
					//Randomize force
					int minX = enc.getInt("force-min-x");
					int minY = enc.getInt("force-min-y");
					int maxX = enc.getInt("force-max-x");
					int maxY = enc.getInt("force-max-y");
					
					int forcex = r.nextInt(maxX-minX); forcex += minX;
					int forcey = r.nextInt(maxY-minY); forcey += minY;
					Vector2 Force = new Vector2(forcex * 10000.0f, forcey * 10000.0f);
					
					//Randomize X position
					int posX = r.nextInt((m_StageLength * (int) Constants.PIXELS_PER_METER) - Constants.GAME_WIDTH);
					posX += Constants.GAME_WIDTH;
					int posY = m_Floor.getHeight() - 35;
					
					//Create encounter based on name
					String encName = enc.getString("name");
					encName = encName.toLowerCase();
					int color = enc.getInt("color");
					if (encName.equals("bunga")) {
						//Create and add to array
						Bunga b = new Bunga(posX, posY, color, Force);
						m_Encounters.add(b);
					} else if (encName.equals("burung")) {
						//Randomize Y position
						posY = r.nextInt(m_StageHeight - m_Floor.getHeight());
						posY += m_Floor.getHeight();
						
						//Randomize speed
						int speed = r.nextInt(30);
						speed += 30; speed *= -1;
						
						//Create and add to array
						Burung b = new Burung(posX, posY, color, Force, (float)speed);
						m_Encounters.add(b);
					} else if (encName.equals("landak")) {
						//Create and add to array
						Landak l = new Landak(posX, posY, color, Force);
						m_Encounters.add(l);
					} else if (encName.equals("lumping")) {
						//Randomize Y position
						posY = r.nextInt(m_StageHeight - m_Floor.getHeight());
						posY += m_Floor.getHeight();
						
						//Randomize speed
						int speed = r.nextInt(10);
						speed += 10; speed *= -1;
						
						//Create and add to array
						Lumping l = new Lumping(posX, posY, color, Force, (float)speed);
						m_Encounters.add(l);
					} else if (encName.equals("stupa")) {
						//Create and add to array
						Stupa s = new Stupa(posX, posY, color, Force);
						m_Encounters.add(s);
					}
				}
			}
		}
	}
	
	//Game constructor from place and num, will load JSON accordingly
	public StateGame(String place, int num) {
		//Initialize stuff
		m_Init 			= false;
		m_Finish		= false;
		m_Paused		= false;
		
		m_GetForce 		= false;	
		
		m_CurrentScore 	= 0;
		m_Time			= 0;
		
		m_Life			= 3;
		
		m_GamePlace		= place;
		m_GameNum		= num;
		
		//Get num according place name
		m_Place = 0;
		if (place.equals("prolog")||place.equals("epilog")) { m_Place = Constants.GAME_PLACE_PROLOG; 		} 
		else if (place.equals("borneo")) 					{ m_Place = Constants.GAME_PLACE_BORNEO; 		} 
		else if (place.equals("bunaken")) 					{ m_Place = Constants.GAME_PLACE_BUNAKEN; 		} 
		else if (place.equals("prambanan")) 				{ m_Place = Constants.GAME_PLACE_PRAMBANAN; 	} 
		else if (place.equals("toba")) 						{ m_Place = Constants.GAME_PLACE_TOBA; 			}
		
		//Get and play music
		if (place.equals("prolog")||place.equals("epilog")||place.equals("prambanan")) m_Music = SoundManager.m_PrologEpilogMusic;
		else if (place.equals("bali")||place.equals("borneo")) m_Music = SoundManager.m_BaliBorneoMusic;
		else if (place.equals("bunaken")||place.equals("toba")) m_Music = SoundManager.m_BunakenTobaMusic;
		
		SoundManager.PlayBGM(m_Music);
		
		//Get sprite
		m_Aira = ResourceManager.m_AiraSprite;
		
		m_SpriteY = 40;
		m_Goal = null;
		if (place.equals("epilog")) {
			if ((num==3)||(num==4)) m_Goal = ResourceManager.m_ButoSprite;
			else if (num==5) m_Goal = ResourceManager.m_GardapatiSprite;
		} else if (m_Place==Constants.GAME_PLACE_PRAMBANAN) {
			m_Goal = ResourceManager.m_GaluvastraSprite;
			m_SpriteY = 45;
		} else if (m_Place==Constants.GAME_PLACE_BUNAKEN) {
			m_Goal = ResourceManager.m_3DewiSprite;
		} else if (m_Place==Constants.GAME_PLACE_TOBA) {
			m_Goal = ResourceManager.m_GatotKacaSprite;
			m_SpriteY = 20;
		} else if (m_Place==Constants.GAME_PLACE_BORNEO) {
			m_Goal = ResourceManager.m_HanomanSprite;
			m_SpriteY = 10;
		}
		
		//Load game condition from external file
		StringBuilder sb = new StringBuilder();
		sb.append(Constants.GAME_LEVEL_FOLDER);
		sb.append(place);
		sb.append("-");
		sb.append(num);
		sb.append(Constants.GAME_LEVEL_EXTENSION);
		
		String JSON = Utilities.ReadJSON(sb.toString());
		
		//Set attributes
		try {
			//Create JSON
			JSONObject m_Object = new JSONObject(JSON);
			
			//Load attributes
			LoadJSON(m_Object, m_Place);
		} catch (JSONException e) {
			e.printStackTrace();
		}		
		
		//create null force
		m_Force = new Vector2();
		
		//Create Cameras
		m_Camera = new OrthographicCamera(Constants.GAME_WIDTH, Constants.GAME_HEIGHT);
		m_Camera.position.set(Constants.GAME_WIDTH / 2, Constants.GAME_HEIGHT / 2, 0);
		
		m_HUDCamera = new OrthographicCamera(Constants.GAME_WIDTH, Constants.GAME_HEIGHT);
		m_HUDCamera.position.set(Constants.GAME_WIDTH / 2, Constants.GAME_HEIGHT / 2, 0);
		
		//Set input processor
		Gdx.input.setInputProcessor(this);
	}
	
	
	//Getter-Setter
	public void setForce(Vector2 force) {
		m_Force = force;
		m_GetForce = true;
	}
	
	@Override
	public void Update() {
		//Update time
		float deltatime = Gdx.graphics.getDeltaTime();
		
		//Update Camera
		m_Camera.update();
		m_HUDCamera.update();
		
		//Update until game was finished
		if (!m_Finish&&!m_Paused) {
			//If already started
			if (m_Init) {
				//Check music
				if (!m_Music.isPlaying()) m_Music.play();
				
				//Update Physics
				m_World.step(deltatime, 3, 3);				
				
				//Update floor
				m_Floor.Update(deltatime);
				
				//Update Garuda
				m_Garuda.Update(deltatime);
				
				//update camera position
				int cameraX = (int) m_Garuda.getPosX(), cameraY = (int) m_Garuda.getPosY();
						
				//keep in boundary
				if (cameraX < Constants.GAME_WIDTH / 2) cameraX = Constants.GAME_WIDTH / 2;
				//else if (cameraX + m_Camera.viewportWidth > m_StageWidth) cameraX = m_StageWidth - (int) m_Camera.viewportWidth;
					
				if (cameraY < Constants.GAME_HEIGHT / 2) cameraY = Constants.GAME_HEIGHT / 2;
				else if (cameraY + m_Camera.viewportHeight > m_StageHeight) cameraY = m_StageHeight - (int) m_Camera.viewportHeight;
						
				//set position
				m_Camera.position.set(cameraX, cameraY, 0);
				
				//Update encounters
				int i = 0;
				while (i<m_Encounters.size()) {
					//Update individual encounter
					m_Encounters.get(i).Update(deltatime);
					
					//Check outer bounds
					if (m_Encounters.get(i).DoesAABBOuterHit(m_Garuda)&&(!m_Encounters.get(i).HasHitten())) {
						//Check inner bounds and act
						if (GameObject.DoesAABBHit(m_Garuda, m_Encounters.get(i))) {
							//Hit
							m_Encounters.get(i).Hit(m_Garuda);
							
							//Add Score
							m_CurrentScore++;
						}
					}
					
					//Delete passed encounters
					if (m_Encounters.get(i).getPosX() + Constants.GAME_WIDTH < m_Garuda.getPosX()) {m_Encounters.remove(i);}
					else i++;
				}
				
				//Update current score and length
				if (m_Time < 0.1f) { m_Time += deltatime; }
				else {
					int score = (int) ((m_Garuda.getPosX() - m_LastPosX) * 2);
					m_LastPosX = m_Garuda.getPosX();
					m_CurrentScore += score;
					m_Time = 0;
					
					m_CurrentLength = ((m_StageLength * (int) Constants.PIXELS_PER_METER) - (int) m_Garuda.getPosX()) / (int) Constants.PIXELS_PER_METER;
				}
				
				//Check game condition
				boolean collision = ((m_Garuda.getPhysicsBody().getLinearVelocity().x==0)
						&&(m_Garuda.getPhysicsBody().getPosition().y<=Math.abs(m_Floor.getPhysicsBody().getPosition().y+m_Floor.getFloorHeight())));
				if (collision||(m_CurrentLength<0)) {
					//Decrease life
					m_Life--;
					
					//Update flag
					if (m_Life==0||m_CurrentLength<0) {
						//Go display end information
						m_Finish=true;
						
						//Stop 
						SoundManager.StopBGM(m_Music);
						
						//Check score
						String info = "";
						if (m_CurrentScore > m_Score1) { info = "AWESOME!";}
						else if ((m_CurrentScore <= m_Score1)&&(m_CurrentScore > m_Score2)) { info = "GREAT!";}
						else if ((m_CurrentScore <= m_Score2)&&(m_CurrentScore > m_Score3)) { info = "NORMAL!";}
						
						boolean finish = (m_CurrentLength==m_StageLength)||(m_CurrentLength<=0);
						
						//Go to state clear
						SetState(new StateClear(m_GamePlace, m_GameNum, m_Place, finish, m_CurrentScore, info));
					}
					else {
						//Reset
						m_Init 		= false;
						m_GetForce 	= false;
					}					
				}
			} 
		}
	}

	@Override
	public void Render() {			
		/*
		 * Draw looping background
		 */
		m_SpriteBatch.setProjectionMatrix(m_Camera.combined);
		m_SpriteBatch.setProjectionMatrix(m_SpriteBatch.getProjectionMatrix().trn(0, 0, 2));
		m_SpriteBatch.begin();
		
		int curr_bg = (int) (m_Garuda.getPosX() / m_BackgroundWidth);
		float curr_bg_x = curr_bg * m_BackgroundWidth;
		
		//Render current 
		m_SpriteBatch.draw(m_Background, curr_bg_x, 0, m_BackgroundWidth, m_BackgroundHeight);
		
		//Render forward/backward if needed
		if (m_Garuda.getPosX() > (curr_bg_x + m_BackgroundWidth - Constants.GAME_WIDTH)) {
			m_SpriteBatch.draw(m_Background, curr_bg_x + m_BackgroundWidth, 0, m_BackgroundWidth, m_BackgroundHeight);
		} else if ((curr_bg!=0) && (m_Garuda.getPosX() < (curr_bg_x + Constants.GAME_WIDTH))) {
			m_SpriteBatch.draw(m_Background, curr_bg_x - m_BackgroundWidth, 0, m_BackgroundWidth, m_BackgroundHeight);
		}
		
		m_SpriteBatch.end();
		
		/*
		 * Draw Game layer : Floor, Garuda, Encounter
		 */
		m_SpriteBatch.setProjectionMatrix(m_Camera.combined);
		m_SpriteBatch.begin();
		
		//Render sprites
		m_SpriteBatch.draw(m_Aira, 40, m_SpriteY);
		if (m_Goal!=null) m_SpriteBatch.draw(m_Goal, m_StageWidth, m_SpriteY);
		
		//Render floor
		m_Floor.Render(m_SpriteBatch, m_Garuda.getPosX());
		
		//Render garuda
		m_Garuda.Render(m_SpriteBatch);
		
		//Render all encounters
		for (int i=0;i<m_Encounters.size();i++) m_Encounters.get(i).Render(m_SpriteBatch);
		
		m_SpriteBatch.end();
		
		/*
		 * Draw HUD
		 */
		m_SpriteBatch.setProjectionMatrix(m_HUDCamera.combined);
		m_SpriteBatch.begin();
		
		//Draw UI
		ResourceManager.m_FontScorePlay.draw(m_SpriteBatch, "Score : " + m_CurrentScore, Constants.GAME_SCORE_X, Constants.GAME_SCORE_Y);
		ResourceManager.m_FontScorePlay.draw(m_SpriteBatch, "Length to Complete : " + m_CurrentLength, Constants.GAME_LENGTH_X, Constants.GAME_LENGTH_Y);
		
		//Draw Information
		if (!m_Init) {
			String info = "Touch screen, Throw, and Touch screen again!";
			int width = ResourceManager.m_FontScorePlay.getBounds(info).width;
			ResourceManager.m_FontScene.draw(m_SpriteBatch, info , (Constants.GAME_WIDTH - width) / 2, Constants.GAME_HEIGHT / 2);
		}
		
		//Draw pause if needed
		if (m_Paused) DrawPauseMenu(m_SpriteBatch);
		
		m_SpriteBatch.end();
	}
	
	private void DrawPauseMenu(SpriteBatch batch) {
		String info = "Game Paused";
		int width = ResourceManager.m_FontScorePlay.getBounds(info).width;
		ResourceManager.m_FontScene.draw(m_SpriteBatch, info , (Constants.GAME_WIDTH - width) / 2, Constants.GAME_HEIGHT / 2);
		
		SoundManager.PauseBGM(m_Music);
	}

	@Override
	public boolean keyDown(int arg0) {

		return false;
	}

	@Override
	public boolean keyTyped(char arg0) {

		return false;
	}

	@Override
	public boolean keyUp(int arg0) {

		return false;
	}

	@Override
	public boolean scrolled(int arg0) {

		return false;
	}
	
	@Override
	public boolean touchDown(int arg0, int arg1, int arg2, int arg3) {	
		//Initialize first if hasn't
		if (!m_Init) {
			if (!m_GetForce) {
				//calculate force
				ForceThread m_Thread = new ForceThread(this);
				m_Thread.start();
			} else {
				//initialize
				m_Init = true;
				
				//apply force
				m_Garuda.Fly();	
				m_Garuda.getPhysicsBody().applyForce(m_Force, m_Garuda.getPosition());
			}
		} else {m_Paused=!m_Paused;}
		return false;
	}

	@Override
	public boolean touchDragged(int arg0, int arg1, int arg2) {

		return false;
	}

	@Override
	public boolean touchMoved(int arg0, int arg1) {

		return false;
	}

	@Override
	public boolean touchUp(int arg0, int arg1, int arg2, int arg3) {

		return false;
	}

	@Override
	public void Dispose() {
		//dispose
		super.Dispose();
		
		Iterator<Body> it = m_World.getBodies();
		while (it.hasNext()) {
			Body b = it.next();
			m_World.destroyBody(b);
		}
		
		//delete world
		m_World.dispose();
	}
}
